package com.aratana;

import java.awt.Component;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ContainerAdapter;
import java.awt.event.ContainerEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;

import javax.swing.AbstractButton;
import javax.swing.FocusManager;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.JTextComponent;

import org.apache.log4j.Logger;

import com.aratana.annotation.AfterAction;
import com.aratana.annotation.BeforeAction;
import com.aratana.ui.ValueChangeListener;
import com.aratana.ui.ViewField;
import com.aratana.ui.fields.ComboField;
import com.aratana.util.ComponentUtilities;

public final class View {

	private final Logger logger;

	private final MVCUnit unit;

	private final Container viewContainer;
	private final HashMap<String, Component> components = new LinkedHashMap<String, Component>();
	private final HashMap<String, Method> beforeActions = new HashMap<String, Method>();
	private final HashMap<String, Method> afterActions = new HashMap<String, Method>();

	View(Container viewContainer, MVCUnit unit) {
		this.viewContainer = viewContainer;
		this.unit = unit;
		logger = Logger.getLogger("aratana." + getUnit().getName() + ".view");
		bindView(viewContainer);
		viewContainer.addContainerListener(new ContainerAdapter() {
			@Override
			public void componentAdded(ContainerEvent e) {
				if (hasName(e.getChild())) {
					bindComponent(e.getChild());
				}
			}
		});
	}

	public MVCUnit getUnit() {
		return unit;
	}

	private void bindView(Container viewContainer) {
		for (Component component : getNamedComponents(viewContainer)) {
			bindComponent(component);
		}

		for (Method method : viewContainer.getClass().getDeclaredMethods()) {
			if (!Modifier.isStatic(method.getModifiers())) {
				BeforeAction beforeAction = method.getAnnotation(BeforeAction.class);
				if (beforeAction != null) {
					String name = beforeAction.name().isEmpty() ? method.getName() : beforeAction.name();
					beforeActions.put(name, method);
				}

				AfterAction afterAction = method.getAnnotation(AfterAction.class);
				if (afterAction != null) {
					String name = afterAction.name().isEmpty() ? method.getName() : afterAction.name();
					afterActions.put(name, method);
				}
			}
		}
	}

	boolean beforeAction(String name) throws Exception {
		boolean result = true;

		Method method = beforeActions.get(name);
		if (method != null) {
			if (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class) {
				result = Boolean.parseBoolean(String.valueOf(method.invoke(viewContainer)));
			} else {
				method.invoke(viewContainer);
			}
		}

		return result;
	}

	void afterAction(String name) throws Exception {

		Method method = afterActions.get(name);
		if (method != null) {
			method.invoke(viewContainer);
		}
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private void bindComponent(final Component component) {
		if (component instanceof ViewField<?>) {
			final ViewField<?> viewField = (ViewField<?>) component;

			viewField.addValueChangeListener(new ValueChangeListener() {
				@Override
				public boolean validateValue(Object newValue) {
					return !viewField.isObrigatory() || newValue != null;
				}

				@Override
				public void valueChanged(Object newValue, Object oldValue) {
					try {
						unit.getModel().set(component.getName(), newValue);
					} catch (Exception ex) {
						unit.handleException(ex);
					}
				}
			});
		} else if (component instanceof AbstractButton) {
			final AbstractButton abstractButton = (AbstractButton) component;

			abstractButton.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					try {
						ComponentUtilities.fireFakeFocusLostListeners(FocusManager.getCurrentManager().getFocusOwner());
						unit.getController().invoke(abstractButton.getName());
					} catch (Exception ex) {
						unit.handleException(ex);
					}
				}
			});

			if (abstractButton instanceof JToggleButton) {
				final JToggleButton toggleButton = (JToggleButton) abstractButton;

				toggleButton.addChangeListener(new ChangeListener() {

					@Override
					public void stateChanged(ChangeEvent e) {
						try {
							unit.getModel().set(toggleButton.getName(), toggleButton.isSelected());
						} catch (Exception ex) {
							unit.handleException(ex);
						}
					}
				});
			}
		} else if (component instanceof JTextComponent) {
			final JTextComponent textComponent = (JTextComponent) component;

			textComponent.addFocusListener(new FocusAdapter() {
				@Override
				public void focusLost(FocusEvent e) {
					try {
						unit.getModel().set(textComponent.getName(), textComponent.getText());
					} catch (Exception ex) {
						unit.handleException(ex);
					}
				}
			});

			if (textComponent instanceof JTextField) {
				final JTextField textField = (JTextField) textComponent;

				textField.addActionListener(new ActionListener() {

					@Override
					public void actionPerformed(ActionEvent e) {
						try {
							unit.getModel().set(textComponent.getName(), textComponent.getText());
						} catch (Exception ex) {
							unit.handleException(ex);
						}
					}
				});
			}
		} else if (component instanceof JComboBox) {
			final JComboBox comboBox = (JComboBox) component;

			comboBox.addItemListener(new ItemListener() {
				@Override
				public void itemStateChanged(ItemEvent e) {
					try {
						if (e.getStateChange() == ItemEvent.SELECTED) {
							unit.getModel().set(comboBox.getName(), e.getItem());
						}
					} catch (Exception ex) {
						unit.handleException(ex);
					}
				}
			});
		} else if (component instanceof JList) {
			// TODO Lists , mais de um item selecionado
		} else if (component instanceof JLabel) {
			JLabel label = (JLabel) component;
			try {
				if (component.getName().startsWith("$")) {
					label.setText(ResourceBundle.getBundle(getUnit().getName()).getString(component.getName().substring(1)));
				} else {
					String text = unit.convert(unit.getModel().get(label.getName()), String.class);
					if (text == null) {
						label.setText("");
					} else if (text.startsWith("$")) {
						label.setText(ResourceBundle.getBundle(getUnit().getName()).getString(text.substring(1)));
					} else {
						label.setText(text);
					}
				}
			} catch (Exception ex) {
				unit.handleException(ex);
			}
		}

		components.put(component.getName(), component);
		logger.debug("Binded component '" + component.getName() + "'.");

		updateFromDatasource(component.getName());
	}

	public Container getViewContainer() {
		return viewContainer;
	}

	public void requestFocus(String name) {
		if (existComponent(name)) {
			components.get(name).requestFocus();
		}
	}

	public void setEnabled(String name, boolean enabled) {
		if (existComponent(name)) {
			components.get(name).setEnabled(enabled);
		}
	}

	public boolean existComponent(String name) {
		return components.containsKey(name);
	}

	public Set<String> listComponents() {
		return components.keySet();
	}

	public Component getComponent(String name) {
		return components.get(name);
	}

	public Collection<Component> getComponents() {
		return components.values();
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public void updateFromDatasource(String name) {
		Component component = components.get(name);
		if (component != null) {
			if (component instanceof ViewField<?>) {
				ViewField<?> viewField = (ViewField<?>) component;
				if (viewField instanceof ComboField<?>) {
					ComboField<?> comboField = (ComboField<?>) viewField;
					try {
						Object obj = getUnit().getModel().getData(name);
						if (obj instanceof List<?>) {
							comboField.addAll((List) obj);
						}
					} catch (Exception ex) {
						unit.handleException(ex);
					}
				}
			}
		}
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public void updateFromModel(String name) {
		Component component = components.get(name);
		if (component != null) {
			try {
				Object value = unit.getModel().get(name);
				if (component instanceof ViewField<?>) {
					ViewField viewField = (ViewField) component;

					viewField.setValue(unit.convert(value, viewField.getFieldClass()));

				} else if (component instanceof JTextComponent) {
					JTextComponent textComponent = (JTextComponent) component;

					textComponent.setText(unit.convert(value, String.class));
				} else if (component instanceof JLabel) {
					JLabel label = (JLabel) component;

					String text = unit.convert(value, String.class);
					if (text == null) {
						label.setText("");
					} else if (text.startsWith("$")) {
						label.setText(ResourceBundle.getBundle(getUnit().getName()).getString(text.substring(1)));
					} else {
						label.setText(text);
					}
				}
			} catch (Exception e) {
				unit.handleException(e);
			}
		}
	}

	private static final List<Component> getNamedComponents(Container viewContainer) {
		ArrayList<Component> comps = new ArrayList<Component>();

		if (viewContainer instanceof JFrame) {
			getNamedComponents(((JFrame) viewContainer).getJMenuBar(), comps);
		} else if (viewContainer instanceof JDialog) {
			getNamedComponents(((JDialog) viewContainer).getJMenuBar(), comps);
		}

		getNamedComponents(viewContainer, comps);

		return comps;
	}

	private static final void getNamedComponents(Container container, List<Component> comps) {
		if (container != null) {
			for (Component component : container.getComponents()) {
				if (hasName(component)) {
					comps.add(component);
				} else if (component instanceof Container) {
					getNamedComponents((Container) component, comps);
					continue;
				}
			}
		}
	}

	private static final boolean hasName(Component component) {
		return component.getName() != null && !component.getName().startsWith("null");
	}
}
